﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ParserCombinatorLibrary
{
    public class Grammar<I>
    {
        public static Parser<R, I> Success<R>(R value)
        {
            return i =>
            {
                return new ParserOutput<R>
                {
                    Result = value,
                    NewPosition = i.CurrentPosition,
                };
            };
        }

        public static Parser<R, I> Fail<R>()
        {
            return i => null;
        }

        public static Parser<I, I> Next()
        {
            return i =>
            {
                if (0 <= i.CurrentPosition && i.CurrentPosition < i.Tokens.Length)
                {
                    return new ParserOutput<I>
                    {
                        Result = i.Tokens[i.CurrentPosition],
                        NewPosition = i.CurrentPosition + 1,
                    };
                }
                return null;
            };
        }

        public static Parser<R, I> Or<R>(Parser<R, I> p, params Parser<R, I>[] ps)
        {
            return ps.Aggregate(p, (a, b) => i => a(i) ?? b(i));
        }

        public static Parser<Optional<R>, I> Opt<R>(Parser<R, I> p)
        {
            return Or(
                from r in p
                select new Optional<R> { Value = r, HasValue = true },

                Success<Optional<R>>(new Optional<R> { HasValue = false })
                );
        }

        public static Parser<Tuple<R1, R2>, I> Seq<R1, R2>(Parser<R1, I> p1, Parser<R2, I> p2)
        {
            return
                from r1 in p1
                from r2 in p2
                select Tuple.Create(r1, r2)
                ;
        }

        public static Parser<R[], I> Any<R>(Parser<R, I> p)
        {
            return Or(
                from r in p
                from rs in Any(p)
                select new R[] { r }.Concat(rs).ToArray(),

                Success<R[]>(new R[] { })
                );
        }

        public static Parser<R[], I> Some<R>(Parser<R, I> p)
        {
            return
                from r in p
                from rs in Any(p)
                select new R[] { r }.Concat(rs).ToArray()
                ;
        }
    }
}
